﻿using MiniFox.Components;
using MiniFox.Web;
using DingTalk.Corp.Auth.Configurations;
using DingTalk.Corp.Auth.Models;
using System.Collections.Specialized;
using DingTalk.Core;

namespace DingTalk.Corp.Auth.Services
{
    /// <summary>
    /// 
    /// </summary>
    public class DingTalkAgentAuthService : DingTalkAgentService, IDingTalkAgentAuthService
    {
        const string USER_TOKEN_KEY = "usertoken";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        public DingTalkAgentAuthService(string name) : base(name)
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="setting"></param>
        public DingTalkAgentAuthService(IDingTalkAgent setting) : base(setting)
        {
        }
        /// <summary>
        /// 
        /// </summary>
        protected string UserTokenCacheKey
        {
            get
            {
                var key = $"{this.Name}#{USER_TOKEN_KEY}";
                return key;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        [AutoConfigure]
        protected DingTalkCorpAuthConfiguration AuthConfiguration { get; set; }


        /// <summary>
        /// 
        /// </summary>
        public Uri OpenAuthUri
        {
            get
            {
                return new Uri(AuthConfiguration.OpenAuthUri);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public Uri QrAuthUri
        {
            get { return new Uri(AuthConfiguration.QrAuthUri); }
        }
        /// <summary>
        /// 
        /// </summary>
        public Uri SessionUri
        {
            get
            {
                return new Uri(AuthConfiguration.SessionUri);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public Uri UserTokenUri
        {
            get
            {
                return new Uri(AuthConfiguration.UserTokenUri);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public Uri UserInfoUri
        {
            get
            {
                return new Uri(AuthConfiguration.UserInfoUri);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public Uri GetUserIdUri
        {
            get
            {
                return new Uri(AuthConfiguration.GetUserIdUri);
            }
        }

        #region create uri
        /// <summary>
        /// 
        /// </summary>
        /// <param name="redirectUri"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public string CreateRedirectUri(string redirectUri, string state)
        {
            return CreateRedirectUri(redirectUri, state, AuthMode.Default);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="redirectUri"></param>
        /// <param name="state"></param>
        /// <param name="authMode"></param>
        /// <returns></returns>
        public virtual string CreateRedirectUri(string redirectUri, string state, AuthMode authMode)
        {
            Uri uri;
            switch (authMode)
            {
                case AuthMode.QrCode:
                    uri = this.QrAuthUri.AddQueryString(new
                    {
                        client_id = this.AppKey,
                        agentid = this.AgentID,
                        redirect_uri = redirectUri,
                        state
                    });
                    break;
                default:
                    uri = this.OpenAuthUri.AddQueryString(new
                    {
                        client_id = this.AppKey,
                        redirect_uri = redirectUri,
                        response_type = "code",
                        scope = this.Setting.AuthScope,
                        state,
                        prompt = "consent"
                    });
                    break;
            }
            return uri.ToString();
        }
        #endregion

        #region user
        /// <summary>
        /// 
        /// </summary>
        /// <param name="authCode"></param>
        /// <returns></returns>
        public Task<SessionResponse> GetSessionAsync(string authCode)
        {
            string accessToken = this.AccessToken;
            var uri = this.SessionUri.AddQueryString(new { access_token = accessToken, js_code = authCode });
            return HttpGetAsync<SessionResponse>(uri);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="authCode"></param>
        /// <returns></returns>
        public async Task<UserInfo> GetUserInfoAsync(string authCode)
        {
            string token = await this.CreateUserTokenAsync(authCode);
            return await GetUserInfoByAccessTokenAsync(token);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        public async Task<UserInfo> GetUserInfoByRefreshTokenAsync(string refreshToken)
        {
            string token = await this.GetUserTokenAsync(refreshToken);
            return await GetUserInfoByAccessTokenAsync(token);
        }

        private async Task<UserInfo> GetUserInfoByAccessTokenAsync(string userAccessToken)
        {
            if (string.IsNullOrEmpty(userAccessToken))
                return null;
            var uri = this.UserInfoUri;
            var headers = new NameValueCollection();
            headers.Add(ApiGlobalKey.HEAD_TOKEN, userAccessToken);
            var userInfo = await uri.HttpGetAsync<UserInfo>(null, headers);
            userInfo.UserID = await GetUserIdAsync(userInfo.UnionID);
            return userInfo;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unionid"></param>
        /// <returns></returns>
        public async Task<string> GetUserIdAsync(string unionid)
        {
            var uri = this.GetUserIdUri.AddQueryString(ApiGlobalKey.ACCESS_TOKEN, this.AccessToken);
            var postData = new { unionid };
            var response = await this.HttpPostAsync<object, UserIdResponse>(uri, postData);
            return response.Result.UserID;
        }
        #endregion

        #region user token
        /// <summary>
        /// 
        /// </summary>
        /// <param name="authCode"></param>
        /// <returns></returns>
        public async Task<string> CreateUserTokenAsync(string authCode)
        {
            var userTokenRequest = new UserTokenRequest
            {
                AppKey = this.AppKey,
                AppSecret = this.AppSecret,
                AuthCode = authCode,
                RefreshToken = string.Empty,
                GrantType = "authorization_code"
            };
            var userTokenResponse = await GetUserTokenAsync(userTokenRequest);
            return userTokenResponse.AccessToken;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        public async Task<string> RefreshUserTokenAsync(string refreshToken)
        {
            var userTokenRequest = new UserTokenRequest
            {
                AppKey = this.AppKey,
                AppSecret = this.AppSecret,
                RefreshToken = refreshToken,
                AuthCode = string.Empty,
                GrantType = "refresh_token"
            };
            var userTokenResponse = await GetUserTokenAsync(userTokenRequest);
            return userTokenResponse.AccessToken;
        }

        private async Task<string> GetUserTokenAsync(string refreshToken)
        {
            var userToken = this.CacheProvider.Get<UserTokenResponse>(refreshToken);
            if (userToken != null)
            {
                if (DateTime.Now >= userToken.RefreshTime)
                {
                    return await this.RefreshUserTokenAsync(refreshToken);
                }
            }
            return userToken.AccessToken;
        }

        private async Task<UserTokenResponse> GetUserTokenAsync(UserTokenRequest userTokenRequest)
        {
            var uri = this.UserTokenUri;
            UserTokenResponse userToken = await this.HttpPostAsync<UserTokenRequest, UserTokenResponse>(uri, userTokenRequest);
            userToken.ExpiresTime = DateTime.Now.AddSeconds(userToken.ExpiresIn);

            if (userToken != null)
            {
                CacheProvider.Remove(userToken.RefreshToken);
                CacheProvider.Add(userToken.RefreshToken, userToken, userToken.ExpiresIn);
            }
            return userToken;
        }
        #endregion

    }
}
